package fitness;

import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.Random;

import model.OligoSystemGeneral;
import optimizers.ganeat.sin.ConstantsSin;
import util.MyUtil;

public class SquareFitness extends SineFitness {
	/**
	 * 
	 */
	private static final long serialVersionUID = 4723276022931522744L;

	protected double getHeight() {
		return ConstantsSin.height;
	}

	@Override
	public double goal(int i, int firstPeak) {
		return ((i - firstPeak + ConstantsSin.period / 4) / (ConstantsSin.period / 2)) % 2 == 0 ? ConstantsSin.height
				: 0;
	}

	public Point2D.Double nextPoint(double x, double y, double[] data) {
		int i = (int) Math.round(x);
		double y1 = data[i + 1];
		double dist = Math.sqrt(1 + (y1 - y) * (y1 - y));
		double dx = 1 / dist;
		double dy = (y1 - y) / dist;
		return new Point2D.Double(x + dx, y + dy);
	}

	protected int closestCorner(int i, int firstPeak) {
		int distanceToLastCorner = (i - firstPeak) % (ConstantsSin.period / 4);
		if (distanceToLastCorner <= ConstantsSin.period / 8) {
			return i - distanceToLastCorner;
		} else {
			return i - distanceToLastCorner + ConstantsSin.period / 4;
		}
	}

	@Override
	public FitnessResult evaluateFitness(OligoSystemGeneral oligoModel) {
		OscillatorBasedResult result = new OscillatorBasedResult();
		result.mse = Double.MAX_VALUE;
		result.count = 0;
		result.limitcycle = .1;
		/*if (oligoModel.nSimpleSequences == 1) {
			result.sde = new Random().nextDouble() / ConstantsSin.penalty;
			result.mse = 1000000;
			return result;
		}*/
		try {
			int output = oligoModel.nameToInt.get("a");
			double[][] data = oligoModel.getTimeSeriesComplexModel();
			int firstPeak = MyUtil.getFirstPeak(data[output],
					ConstantsSin.skipTime);
			if (firstPeak == -1
					|| firstPeak + ConstantsSin.period
							* (ConstantsSin.numberofperiods + 1) > data[output].length) {
				int firstPeak2 = MyUtil.getFirstPeak(data[output], 0);
				if (firstPeak2 == -1) {
					result.sde = new Random().nextDouble()
							/ ConstantsSin.penalty;
					result.mse = 1000000;
					return result;
				} else {
					result.sde = (double) 1 / ConstantsSin.penalty;
					result.mse = 100000;
					return result;
				}
			} else {
				int nextPeak = MyUtil.getFirstPeak(data[output], firstPeak
						+ ConstantsSin.period * ConstantsSin.numberofperiods);
				if (nextPeak != -1) {
					result.limitcycle = 1
							/ (1 + Math.abs(data[output][firstPeak]
									- data[output][nextPeak])
									/ data[output][firstPeak])
							/ (1 + Math.abs(data[output][firstPeak]
									- data[output][nextPeak])
									/ data[output][firstPeak]); // changed YR
				}
			}

			double mse = 0;
			double average = 0;
			double averageGoal = 0;

			int calculationCount = 0;
			for (int i = Math.max(firstPeak, ConstantsSin.skipTime); i < Math
					.min(firstPeak + ConstantsSin.period
							* (ConstantsSin.numberofperiods + 1),
							data[output].length - 1); i++) {
				average += data[output][i];
				averageGoal += goal(i, firstPeak);
				calculationCount++;
			}

			average = average / calculationCount;
			averageGoal = averageGoal / calculationCount;
			double standdev = (double) 1 / ConstantsSin.penalty;
			double standdevGoal = 0;
			int count = 0;

			int i = Math.max(firstPeak, ConstantsSin.skipTime);
			double x = i;
			double y = data[output][i];
			while (x < Math.min(firstPeak + ConstantsSin.period
					* (ConstantsSin.numberofperiods + 1),
					data[output].length - 1)) {
				Point2D.Double point = nextPoint(x, y, data[output]);
				x = point.x;
				y = point.y;
				double goal = goal((int) Math.round(x), firstPeak);
				double error = Math
						.min(Math.abs(goal - y),
								y < getHeight() ? Math.abs(x
										- closestCorner((int) Math.round(x),
												firstPeak)) : Double.MAX_VALUE);
				// System.out.println(x + "," + y + ":" + error);
				mse += error * error;
				standdev += (average - data[output][i])
						* (average - data[output][i]);
				standdevGoal += (averageGoal - goal) * (averageGoal - goal);
				count++;
			}
			standdev /= count;
			result.mse = mse / count;
			result.sde = Math.min(5, standdev);
			if (firstPeak == -1) {
				result.sde /= ConstantsSin.penalty;
			}

		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return result;
	}

}
